<template>
  <div id="data-source">
    <div class="data-source-left-panel">
      <div class="title">
        <span>状态管理</span>
        <link-button :href="docsUrl"></link-button>
        <close-icon @close="closePanel"></close-icon>
      </div>
      <tiny-tabs v-model="activeName" @click="tabClick" tab-style="button-card">
        <tiny-tab-item v-if="isBlock" :name="STATE.CURRENT_STATE" title="区块状态"></tiny-tab-item>
        <tiny-tab-item v-else :name="STATE.CURRENT_STATE" title="页面状态"></tiny-tab-item>
        <tiny-tab-item :name="STATE.GLOBAL_STATE" title="应用状态"></tiny-tab-item>
      </tiny-tabs>
      <tiny-search
        :modelValue="query"
        class="left-filter"
        placeholder="请输入搜索条件"
        @update:modelValue="search"
      ></tiny-search>
      <div class="add-btn">
        <tiny-button @click="openPanel(OPTION_TYPE.ADD)">{{
          activeName === STATE.CURRENT_STATE ? '添加变量' : '添加全局变量'
        }}</tiny-button>
      </div>
      <data-source-list
        :modelValue="Object.keys(state.dataSource)"
        :stateScope="activeName"
        :query="query"
        @openPanel="openPanel"
        @remove="remove"
        @removeStore="removeStore"
      />
    </div>
    <div v-if="isPanelShow" class="data-source-right-panel">
      <div class="header">
        <span>{{ addDataSource }}</span>
        <span class="options-wrap">
          <tiny-button type="danger" @click="confirm">保存</tiny-button>
          <close-icon @close="cancel"></close-icon>
        </span>
      </div>
      <create-variable
        v-if="activeName === STATE.CURRENT_STATE"
        ref="variableRef"
        :dataSource="state.dataSource"
        :flag="flag"
        :updateKey="updateKey"
        :createData="state.createData"
        @nameInput="updateName"
      />
      <create-store
        v-if="activeName === STATE.GLOBAL_STATE"
        ref="storeRef"
        :dataSource="state.dataSource"
        :flag="flag"
        :updateKey="updateKey"
        :storeData="state.createData"
        @nameInput="validName"
      />
    </div>
  </div>
</template>

<script>
import { reactive, ref, computed, onActivated } from 'vue'
import { Button, Search, Tabs, TabItem } from '@opentiny/vue'
import {
  useCanvas,
  useHistory,
  useEditorInfo,
  useResource,
  useNotify,
  useData,
  useLayout,
  useHelp
} from '@opentiny/tiny-engine-controller'
import { setState, getSchema, deleteState, setGlobalState, getGlobalState } from '@opentiny/tiny-engine-canvas'
import { CloseIcon, LinkButton } from '@opentiny/tiny-engine-common'
import DataSourceList from './DataSourceList.vue'
import CreateVariable from './CreateVariable.vue'
import CreateStore from './CreateStore.vue'
import { updateGlobalState } from './js/http'
import { STATE, OPTION_TYPE } from './js/constants'
import { validateMonacoEditorData } from './js/common'

export default {
  components: {
    TinySearch: Search,
    TinyButton: Button,
    DataSourceList,
    CreateVariable,
    CloseIcon,
    TinyTabs: Tabs,
    TinyTabItem: TabItem,
    CreateStore,
    LinkButton
  },
  setup(props, { emit }) {
    const variableRef = ref(null)
    const storeRef = ref(null)
    const isPanelShow = ref(false)
    const errorMessage = ref('')
    const flag = ref('')
    const query = ref('')
    const updateKey = ref('')
    const addDataSource = ref('添加变量')
    const activeName = ref(STATE.CURRENT_STATE)
    const isBlock = computed(() => useCanvas().isBlock())
    const { setSaved } = useCanvas()
    const { PLUGIN_NAME, getPluginApi } = useLayout()
    const { openCommon } = getPluginApi(PLUGIN_NAME.save)
    const docsUrl = useHelp().getDocsUrl('data')
    const state = reactive({
      dataSource: {},
      createData: {
        name: '',
        description: '',
        variable: ''
      }
    })

    const openPanel = (flagValue, key = '') => {
      updateKey.value = key
      flag.value = flagValue
      const isCurrent = activeName.value === STATE.CURRENT_STATE
      if (flagValue === OPTION_TYPE.ADD) {
        state.createData.name = ''
        state.createData.variable = ''
        errorMessage.value = ''
        addDataSource.value = isCurrent ? '添加变量' : '添加全局变量'
      } else if (flagValue === OPTION_TYPE.UPDATE) {
        state.createData.name = key
        state.createData.variable = state.dataSource[key]
        addDataSource.value = isCurrent ? '修改变量' : '修改全局变量'
      } else {
        state.createData.name = `${key}_copy`
        state.createData.variable = state.dataSource[key]
        addDataSource.value = isCurrent ? '复制变量' : '复制全局变量'
      }

      isPanelShow.value = true
    }

    const cancel = () => {
      errorMessage.value = ''
      isPanelShow.value = false
    }

    const add = (name, variable) => {
      if (getSchema()) {
        if (updateKey.value !== name && flag.value === OPTION_TYPE.UPDATE) {
          delete state.dataSource[updateKey.value]
        }
        state.dataSource[name] = variable
      }
    }

    const validName = (name) => {
      errorMessage.value = name
    }

    const notifySaveError = (message) => {
      useNotify({
        title: '保存错误',
        type: 'error',
        message
      })
    }

    const updateName = (value) => {
      state.createData.name = value
    }

    const confirm = () => {
      const { name } = state.createData

      if (!name || errorMessage.value) {
        notifySaveError('变量名未填写或名称不符合规范，请按照提示修改后重试。')
        return
      }

      if (activeName.value === STATE.CURRENT_STATE) {
        // 校验
        const validateResult = variableRef.value.validate()
        if (!validateResult.success) {
          notifySaveError(validateResult.message)
          return
        }

        // 获取数据
        let variable = variableRef.value.getFormData()

        // 保存数据
        add(name, variable)
        isPanelShow.value = false
        setSaved(false)

        // 触发画布渲染
        setState({ [name]: variable })
        useHistory().addHistory()
      } else {
        const validateResult = validateMonacoEditorData(storeRef.value.getEditor(), 'state字段', { required: true })
        if (!validateResult.success) {
          notifySaveError(validateResult.message)
          return
        }

        const storeState = storeRef.value.getEditor().getValue()
        const getters = storeRef.value.saveMethods('gettersEditor')
        const actions = storeRef.value.saveMethods('actionsEditor')
        const store = {
          [name]: {
            id: name,
            state: storeState,
            getters,
            actions
          }
        }

        if (updateKey.value !== name && flag.value === OPTION_TYPE.UPDATE) {
          delete state.dataSource[updateKey.value]
        }

        Object.assign(state.dataSource, store)
        const storeList = Object.values(state.dataSource)

        const { id } = useEditorInfo().useInfo()
        updateGlobalState(id, { global_state: storeList }).then((res) => {
          isPanelShow.value = false
          setGlobalState(res.global_state || [])
        })
      }
      openCommon()
    }

    const search = (value) => {
      if (value === undefined) {
        return
      }

      query.value = value
    }

    const remove = (key) => {
      delete state.dataSource[key]
      // 删除变量也需要同步触发画布渲染
      deleteState(key)

      if (key.startsWith('datasource')) {
        const pageSchema = getSchema()
        const { getCommentByKey } = useData()
        const { start, end } = getCommentByKey(key)

        /**
         * 匹配提前注入的 loadDataSource 表达式和注释，级联删除
         * 等价：/([\s\n]*\/\*\* start \*\/[\s\S]*\/\*\* end \*\/)/
         * "任意换行或空白字符 /** start-key *\/ 任意字符 /** end-key *\/"，该字符串会被匹配
         */
        const pattern = new RegExp(`([\\s\\n]*\\/\\*\\* ${start} \\*\\/[\\s\\S]*\\/\\*\\* ${end} \\*\\/)`)

        pageSchema.lifeCycles.setup.value = pageSchema.lifeCycles.setup.value.replace(pattern, '')
      }

      // 如果删除的是当前编辑的状态变量，则需要关闭二级面板
      if (state.createData.name === key) {
        isPanelShow.value = false
      }

      setSaved(false)
    }

    const setGlobalStateToDataSource = () => {
      const globalState = getGlobalState()

      if (!globalState) {
        state.dataSource = {}

        return
      }

      state.dataSource = getGlobalState().reduce((acc, store) => ({ ...acc, [store.id]: store }), {})
    }

    const removeStore = (key) => {
      const storeListt = [...useResource().resState.globalState] || []
      const index = storeListt.findIndex((store) => store.id === key)

      if (index !== -1) {
        const { id } = useEditorInfo().useInfo()

        storeListt.splice(index, 1)
        updateGlobalState(id, { global_state: storeListt }).then((res) => {
          setGlobalState(res.global_state)
          setGlobalStateToDataSource()
        })

        // 如果删除的是当前编辑的状态变量，则需要关闭二级面板
        if (state.createData.name === key) {
          isPanelShow.value = false
        }
      }
    }

    const closePanel = () => {
      emit('close')
    }

    const initDataSource = (tabsName = activeName.value) => {
      if (tabsName === STATE.GLOBAL_STATE) {
        setGlobalStateToDataSource()
      } else {
        const pageSchema = getSchema() || {}

        pageSchema.state = pageSchema?.state || {}
        state.dataSource = pageSchema.state
      }
    }

    const tabClick = () => {
      isPanelShow.value = false
      query.value = ''
      initDataSource()
    }

    onActivated(() => {
      initDataSource()
    })

    return {
      isBlock,
      isPanelShow,
      errorMessage,
      state,
      variableRef,
      addDataSource,
      updateName,
      openPanel,
      cancel,
      confirm,
      search,
      query,
      remove,
      closePanel,
      validName,
      flag,
      updateKey,
      activeName,
      tabClick,
      STATE,
      removeStore,
      storeRef,
      OPTION_TYPE,
      open,
      docsUrl
    }
  }
}
</script>

<style lang="less" scoped>
#data-source {
  height: 100%;
  position: relative;

  .data-source-left-panel {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    .add-btn {
      margin: 10px 0px 0px 10px;
      max-width: none;
    }

    .title {
      padding: 10px;
      font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
        'Helvetica Neue', sans-serif;
      color: var(--ti-lowcode-plugin-panel-title-color);
      font-weight: var(--ti-lowcode-plugin-panel-title-font-weight);
      border-bottom: 1px solid var(--ti-lowcode-data-header-border-bottom-color);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .left-filter {
      margin-top: 12px;
      padding: 0 10px;
    }

    & > span {
      display: inline-block;
      padding: 0 10px;
      &,
      .left-btn,
      :deep(.tiny-popover__reference) {
        width: 100%;
      }
    }
    .left-btn {
      max-width: 100%;
      margin-top: 12px;
    }
  }

  .data-source-right-panel {
    width: 492px;
    height: 100%;
    border-right: 1px solid var(--ti-lowcode-toolbar-border-color);
    background: var(--ti-lowcode-common-component-bg);
    position: absolute;
    left: calc(var(--base-left-panel-width) - 6px);
    top: 0;

    .header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 45px;
      padding: 0 12px;
      color: var(--ti-lowcode-toolbar-icon-color);
      background: var(--ti-lowcode-common-component-bg);
      border-bottom: 1px solid var(--ti-lowcode-data-header-border-bottom-color);
      .options-wrap {
        display: flex;
        column-gap: 16px;
        align-items: center;
      }
    }
  }

  :deep(.tiny-tabs__header) {
    padding: 8px;
  }

  :deep(.tiny-tabs__header .tiny-tabs__active-bar) {
    bottom: auto;
    top: 0;
    height: 2px;
    background-color: transparent;
  }

  :deep(.tiny-tabs__header .tiny-tabs__nav-wrap::after) {
    content: none;
  }

  :deep(.tiny-tabs__item) {
    flex: 1 1 auto;
    text-align: center;
    color: var(--ti-lowcode-common-primary-text-color);
    &:not(.is-active) {
      background-color: var(--ti-lowcode-data-radio-group-bg);
    }
  }

  :deep(.tiny-tabs__nav) {
    float: none;
    display: flex;
    flex-wrap: wrap;
    .tiny-tabs__item {
      &.is-active {
        background-color: var(--ti-lowcode-data-radio-group-active-bg);
      }
    }
  }

  :deep(.tiny-tabs__content) {
    height: calc(100% - 48px);
    padding: 0;
    & > div {
      height: 100%;
    }
  }
  :deep(.help-box) {
    position: absolute;
    left: 70px;
    top: 11px;
  }
}
</style>
